Denobook 01
https://gyazo.com/7269016e457c2a2a5d06f2c91903351e
Denoは、2018年夏頃登場した、新進気鋭のTypeScript / JavaScriptランタイムです。
同人誌として、Denoのみをテーマに技術書を出版するのは、もしかしたら本書が初めてかも…?
本の内容としては、Deno初心者向け〜実践的な内容の幅広いものとなっています。
実践的とは言っても、使用言語はTypeScriptなので、普段からJS / TSを書いていれば難なく読めるのではないかと思います。
本書は三章の構成になっていて、それぞれ別の著者による記事となります。各章の関連性はありませんが、後に進むほど実践的な内容となっています。
第一章: Denoの紹介 (hashrock著)
Denoとは何なのか?と言う根本的な疑問から、挿絵付きで優しく学べます。主にNode.jsユーザー向けの内容となります。
第二章: DenoのI/Oについて (syumai著)
DenoのCoreに含まれるReader / Writerと、その周辺ツールの解説を行います。Denoのmoduleを作成する上で役に立つ、実践的な内容となります。
第三章: Deno De HTTP Server (keroxp著)
Denoの標準module(deno_std)に多数Contributeしているkeroxpによる、HTTPサーバーの実装に踏み込んだ大ボリュームの記事です。Denoにおけるネットワーク処理の実装方法について解説されており、非常に実践的な内容です。それだけでなく、HTTPについての基礎的な話もふんだんに含まれているので、HTTPの周辺領域の知識が欲しい、と言う方にとってもうってつけの内容です。
書籍情報
タイトル: Denobook 01
ページ数: 48p (表紙除く)
価格: 500円
著者
表紙
hashrock
編集・原稿ビルド環境構築
keroxp
著者の声
無事校了しました!!2019/4/2keroxp.icon
ヤッター!!!syumai.icon
祝!hashrock.icon
サンプルコード
第2章 Deno の I/O について by syumai.icon
Node.jsの場合
code:js
const fs = require('fs');
// Stream を使う
const src = fs.createReadStream('example.txt');
src.pipe(process.stdout);
Denoの場合
code:ts
const { open, copy, stdout } = Deno;
// Reader / Writer を使う
(async () => {
const src = await open('example.txt');
await copy(stdout, src);
})();
copyを使う
code:ts
const { copy, open, stdout } = Deno;
// ダメな例 (Fileの内容がぐちゃぐちゃに表示される)
(async () => {
for (const fileName of fileNames) {
const f = await open(fileName);
copy(stdout, f); // awaitしていないので、すぐに次のFileに進む
// f.close(); ここでcloseするとcopyに失敗すると言う別の問題もある
}
})();
// 正しい例
(async () => {
for (const fileName of fileNames) {
const f = await open(fileName);
await copy(stdout, f);
f.close();
}
})();
fetchとcopyを組み合わせる
code:ts
const { copy, stdout, open } = Deno;
(async () => {
// OpenModeの w+ は、Fileがあればtruncateし、頭から書き込みを行います。無ければ作成します
const f = await open('denoland.html', 'w+');
await copy(f, res.body);
f.close();
})();
Buffer
code:ts
const { copy, stdout } = Deno;
const enc = new TextEncoder();
(async () => {
const buf = new Deno.Buffer();
await buf.write(enc.encode('hello, '));
await buf.write(enc.encode('deno '));
await buf.write(enc.encode('world!'));
await copy(stdout, buf); // hello, deno world!
})();
code:ts
const { open, copy, readAll, stdout } = Deno;
class FileCache {
// Fileをキャッシュする
async store(path: string) {
console.log(store: ${path});
const f = await open(path);
const bytes = await readAll(f); // Fileの内容をbyte列として全て読み出す
f.close();
}
// キャッシュからFileのデータを読み出して返す。改めてFileをopenすることはしない。
async load(path: string): Promise<Deno.Reader> {
const bytes = this.cachepath; // まだキャッシュされていなければ格納してから返す
if (!bytes) {
await this.store(path);
return await this.load(path);
}
// byte列をReaderとして読めるようにして返す
return new Deno.Buffer(bytes);
}
}
(async () => {
const fc = new FileCache();
await fc.load('index.html'); // 1回目のロードでFileがキャッシュされる
await copy(stdout, await fc.load('index.html')); // 2回目のロードではFileはopenされない
})();
//}
StringReader
code:ts
const { open, copy } = Deno;
import {
StringReader,
(async () => {
const f = await open('example.html', 'w+');
const html = <html><body>hello, world!</body></html>;
const rd = new StringReader(html);
await copy(f, rd);
f.close();
})();
MultiReader
code:ts
const { open, copy } = Deno;
import {
MultiReader,
StringReader,
(async () => {
const header = await open('header.html');
const body = new StringReader('hello, multi reader!');
const footer = await open('footer.html');
// header, body, footer を連結して一つのReaderにする
const html = new MultiReader(header, body, footer);
const index = await open('index.html', 'w+');
await copy(index, html); // 結合結果をindex.htmlに書き込む
header.close();
footer.close();
index.close();
})();
Readerの実装例
code:ts
const { stdout, copy } = Deno;
import {
StringReader,
class UpcaseReader {
constructor(private readonly reader: Deno.Reader) {}
async read(p: Uint8Array): Promise<Deno.ReadResult> {
// ベースのreaderから、pにデータを読み取る
const { nread, eof } = await this.reader.read(p);
for (let i = 0; i < p.length; i++) {
// アルファベットの小文字の範囲内か見る
if (pi >= 97 && pi <= 122) { }
}
return { nread, eof };
}
}
(async () => {
const msg = 'Hello, World!';
const reader = new UpcaseReader(new StringReader(msg));
// UpcaseReaderはReaderを実装しているので、そのままcopyに渡せる
await copy(stdout, reader); // HELLO, WORLD!
})();
Writerの実装例
code:ts
const { stdout, copy } = Deno;
import {
StringReader,
class DowncaseWriter {
constructor(private readonly writer: Deno.Writer) {}
async write(p: Uint8Array): Promise<number> {
// pと同じ長さのUint8Arrayを用意する
const buf = new Uint8Array(p.length);
for (let i = 0; i < p.length; i++) {
// アルファベットの大文字の範囲内か見る
if (pi >= 65 && pi <= 90) { // 小文字に変換してbufに書き込む
continue;
}
// アルファベットの大文字の範囲内でなければ変換処理をしない
}
// WrapしたWriterに書き込んだ結果をそのまま返す
return await this.writer.write(buf);
}
}
(async () => {
const msg = 'Hello, World!';
const writer = new DowncaseWriter(stdout);
// DowncaseWriterはWriterを実装しているので、そのままcopyに渡せる
await copy(writer, new StringReader(msg)); // hello, world!
})();
第3章 Deno de HTTP Server by keroxp.icon
3.5 まずはTCPサーバーから
code:echo_client.ts
async function echo_client() {
// 2-1
const conn = await Deno.dial("tcp", "127.0.0.1:8888");
// 2-2
const encoder = new TextEncoder();
const decoder = new TextDecoder();
// 2-3
await conn.write(encoder.encode("hello!"));
const buffer = new Uint8Array(1024);
const { eof, nread } = await conn.read(buffer);
const reply = decoder.decode(buffer.slice(0, nread));
console.log(reply); // => hello!
}
echo_client();
code:echo_server.ts
async function echo_server() {
// 1-1
const listener = Deno.listen("tcp", "127.0.0.1:8888");
// 1-2
const conn = await listener.accept();
// 1-3
const buffer = new Uint8Array(1024);
const { eof, nread } = await conn.read(buffer);
// 1-4
await conn.write(buffer.slice(0, nread));
// 1-5
conn.close();
}
echo_server();
3.7実装する
code:http_server.ts
// 3-0
async function readLine(bufReader: BufReader): Promise<string> {
if (!ok && err) {
throw err
}
return new TextDecoder().decode(line)
}
async function http_echo_server() {
const listener = Deno.listen("tcp", "127.0.0.1:8888");
const conn = await listener.accept();
// 4-1
const bufReader = new BufReader(conn);
const bufWriter = new BufWriter(conn);
const encoder = new TextEncoder();
// 4-2
const requestLine = await readLine(bufReader);
const method, pathname, version = requestLine.match(/^(^ +)? (^ +?) (^ +?)$/); // 4-3
let headerLine: string;
const requestHeaders = new Headers();
while ((headerLine = await readLine(bufReader)).length > 0) {
const key, value = headerLine.split(":").map(s => s.trim()); requestHeaders.set(key, value)
}
// 4-4
const contentLength = parseInt(requestHeaders.get("content-length"));
const bodyBuf = new Uint8Array(contentLength);
await bufReader.readFull(bodyBuf);
// 4-5
await bufWriter.write(encoder.encode(
HTTP/1.1 200 OK\r\n
));
// 4-6
for (const key, value of requestHeaders.entries()) { await bufWriter.write(encoder.encode(
${key}: ${value}\r\n
))
}
await bufWriter.write(encoder.encode("\r\n"));
// 4-7
await bufWriter.write(bodyBuf);
await bufWriter.flush();
// 4-8
conn.close();
}
http_echo_server();
3.8 Echo HTTPサーバーにしてみる
code:http_echo_server.ts
// 3-0
async function readLine(bufReader: BufReader): Promise<string> {
if (!ok && err) {
throw err
}
return new TextDecoder().decode(line)
}
async function http_server() {
const listener = Deno.listen("tcp", "127.0.0.1:8888");
const conn = await listener.accept();
// 3-1
const bufReader = new BufReader(conn);
const bufWriter = new BufWriter(conn);
const encoder = new TextEncoder();
// 3-2
const requestLine = await readLine(bufReader);
const method, pathname, version = requestLine.match(/^(^ +)? (^ +?) (^ +?)$/); // 3-3
let headerLine: string;
const requestHeaders = new Headers();
while ((headerLine = await readLine(bufReader)).length > 0) {
const key, value = headerLine.split(":").map(s => s.trim()); requestHeaders.set(key, value)
}
// 3-4
await bufWriter.write(encoder.encode(
HTTP/1.1 200 OK\r\n
));
// 3-5
const responseBody = encoder.encode("Hello!");
const responseHeaders = new Headers({
"Content-Type": "text/plain",
"Content-Length": ${responseBody.byteLength}
});
for (const key, value of responseHeaders.entries()) { await bufWriter.write(encoder.encode(
${key}: ${value}\r\n
))
}
await bufWriter.write(encoder.encode("\r\n"));
// 3-6
await bufWriter.write(responseBody);
await bufWriter.flush();
// 3-7
conn.close();
}
http_server();